{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-02-07T02:15:12.817138Z",
     "start_time": "2025-02-07T02:15:09.101611Z"
    }
   },
   "source": [
    "# 导入库\n",
    "import matplotlib as mpl\n",
    "# Matplotlib 绘制的图形会直接嵌入到 Notebook 的输出单元格中，而不是弹出一个独立的窗口\n",
    "%matplotlib inline\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "\n",
    "# os库提供了一种使用操作系统相关功能的便捷方式，允许与操作系统进行交互\n",
    "import os\n",
    "\n",
    "# sys库主要用于处理Python运行时的环境相关信息以及与解释器的交互\n",
    "import sys\n",
    "import time\n",
    "\n",
    "# tqdm库是一个快速，可扩展的Python进度条，可以在 Python 长循环中添加一个进度提示信息，用户只需要封装任意的迭代器 tqdm(iterator)，即可获得一个进度条显示迭代进度。\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "\n",
    "# torch.nn是PyTorch中用于构建神经网络的模块\n",
    "import torch.nn as nn\n",
    "\n",
    "# torch.nn.functional是PyTorch中包含了神经网络的一些常用函数\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.6.0+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:15:37.350657Z",
     "start_time": "2025-02-07T02:15:37.338534Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home='data')\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "id": "17fc2cf47b5c5bbc",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 4
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:15:38.057511Z",
     "start_time": "2025-02-07T02:15:38.053352Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# print(housing.data[0:5])\n",
    "import pprint  #打印的格式比较 好看\n",
    "\n",
    "pprint.pprint(housing.data[0:5])\n",
    "print('-' * 50)\n",
    "pprint.pprint(housing.target[0:5])"
   ],
   "id": "a08a7c8b083b4029",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "array([[ 8.32520000e+00,  4.10000000e+01,  6.98412698e+00,\n",
      "         1.02380952e+00,  3.22000000e+02,  2.55555556e+00,\n",
      "         3.78800000e+01, -1.22230000e+02],\n",
      "       [ 8.30140000e+00,  2.10000000e+01,  6.23813708e+00,\n",
      "         9.71880492e-01,  2.40100000e+03,  2.10984183e+00,\n",
      "         3.78600000e+01, -1.22220000e+02],\n",
      "       [ 7.25740000e+00,  5.20000000e+01,  8.28813559e+00,\n",
      "         1.07344633e+00,  4.96000000e+02,  2.80225989e+00,\n",
      "         3.78500000e+01, -1.22240000e+02],\n",
      "       [ 5.64310000e+00,  5.20000000e+01,  5.81735160e+00,\n",
      "         1.07305936e+00,  5.58000000e+02,  2.54794521e+00,\n",
      "         3.78500000e+01, -1.22250000e+02],\n",
      "       [ 3.84620000e+00,  5.20000000e+01,  6.28185328e+00,\n",
      "         1.08108108e+00,  5.65000000e+02,  2.18146718e+00,\n",
      "         3.78500000e+01, -1.22250000e+02]])\n",
      "--------------------------------------------------\n",
      "array([4.526, 3.585, 3.521, 3.413, 3.422])\n"
     ]
    }
   ],
   "execution_count": 5
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:16:01.605357Z",
     "start_time": "2025-02-07T02:16:01.597986Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# train_test_split 是用于将数据集随机拆分为训练集和测试集的工具\n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "# 将完整数据集 housing.data 和对应的标签 housing.target 拆分为训练集（x_train_all 和 y_train_all）和测试集（x_test 和 y_test）\n",
    "#保留一部分数据作为测试集，用于最终评估模型的性能\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state=7)\n",
    "\n",
    "# 将训练集 x_train_all 和 y_train_all 进一步拆分为训练集（x_train 和 y_train）和验证集（x_valid 和 y_valid）\n",
    "# 验证集用于在训练过程中评估模型性能，调整超参数，防止过拟合\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state=11)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train],  #训练集\n",
    "    \"valid\": [x_valid, y_valid],  #验证集\n",
    "    \"test\": [x_test, y_test],  #测试集\n",
    "}  #把3个数据集都放到字典中"
   ],
   "id": "1d13ee18ec338b7c",
   "outputs": [],
   "execution_count": 7
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:16:11.223019Z",
     "start_time": "2025-02-07T02:16:11.213870Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "\n",
    "scaler = StandardScaler()  #标准化\n",
    "\n",
    "# fit 方法：计算数据的统计量（如均值、标准差等）或学习数据的特征（如 PCA 的主成分），但不对数据进行转换。\n",
    "# transform 方法：使用 fit 方法学习到的统计量或特征对数据进行转换。\n",
    "# fit_transform 方法：先调用 fit 方法学习统计量或特征，然后调用 transform 方法对数据进行转换。\n",
    "scaler.fit(x_train)"
   ],
   "id": "6103b1a599cbd821",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-1 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-1 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-1 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-1 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-1 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-1 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 8
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "# 构建数据集\n",
    "\n",
    "这里构建多输入的数据集，注意到数据集介绍里对每个特征定义如下：\n",
    "\n",
    "> **Attribute Information**:\n",
    "> - MedInc        median income in block group\n",
    "> - HouseAge      median house age in block group\n",
    "> - AveRooms      average number of rooms per household\n",
    "> - AveBedrms     average number of bedrooms per household\n",
    "> - Population    block group population\n",
    "> - AveOccup      average number of household members\n",
    "> - Latitude      block group latitude\n",
    "> - Longitude     block group longitude\n",
    "\n",
    "认为最后两维作为位置信息要单独处理，故制作数据集如下"
   ],
   "id": "2ee21e0b9128d697"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:35:15.588769Z",
     "start_time": "2025-02-07T02:35:15.579340Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 从PyTorch的utils.data模块导入Dataset类，并定义一个名为HousingDataset的新类，继承自Dataset。\n",
    "# Dataset是PyTorch中用于表示数据集的抽象基类\n",
    "# 通过继承Dataset类，我们可以自定义数据集的行为，使其与PyTorch的数据加载器兼容。\n",
    "from torch.utils.data import Dataset\n",
    "\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "\n",
    "    # 初始化方法，根据指定的模式加载数据，并对数据进行预处理\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1)\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.x)\n",
    "\n",
    "    def __getitem__(self, idx):\n",
    "        # 返回的是一个元组，元组里面是两个元素，第一个元素是一个元组，第二个元素是一个数。self.x[idx][-2:]代表取最后两个元素\n",
    "        return (self.x[idx], self.x[idx][-2:]), self.y[idx]\n",
    "\n",
    "\n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "id": "b69d8c224832fd3f",
   "outputs": [],
   "execution_count": 9
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:35:23.110380Z",
     "start_time": "2025-02-07T02:35:23.104339Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[1]",
   "id": "49ec367d7cc3dc39",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "((tensor([-0.2981,  0.3523, -0.1092, -0.2506, -0.0341, -0.0060,  1.0806, -1.0611]),\n",
       "  tensor([ 1.0806, -1.0611])),\n",
       " tensor([1.5140]))"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 10
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# DataLoader",
   "id": "aea3d36461299724"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:35:51.899904Z",
     "start_time": "2025-02-07T02:35:51.896571Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "batch_size = 8\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=False)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "id": "652b2c61196a54d6",
   "outputs": [],
   "execution_count": 11
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 定义模型",
   "id": "524a20804b643932"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "功能: 定义了一个改进版的 Wide & Deep 模型，分离了 Wide 部分和 Deep 部分的输入特征。\n",
    "\n",
    "原理:\n",
    "\n",
    "Wide 部分直接使用原始特征，捕捉低阶特征交互。\n",
    "\n",
    "Deep 部分通过多层神经网络提取高阶特征交互。\n",
    "\n",
    "将 Wide 部分和 Deep 部分的特征拼接后，通过输出层得到最终结果。\n",
    "\n",
    "作用:\n",
    "\n",
    "适用于推荐系统、分类/回归任务等。\n",
    "\n",
    "能够同时捕捉低阶和高阶特征交互。\n",
    "\n",
    "为什么这样做:\n",
    "\n",
    "分离 Wide 部分和 Deep 部分的输入，使模型更灵活。\n",
    "\n",
    "通过参数初始化和拼接特征，增强模型的表达能力和训练稳定性。"
   ],
   "id": "6787a40807dbef0"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:47:48.923019Z",
     "start_time": "2025-02-07T02:47:48.918509Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#回归模型我们只需要1个数\n",
    "# 定义一个名为 WideDeep 的类，继承自 nn.Module\n",
    "class WideDeep(nn.Module):\n",
    "\n",
    "    # 定义类的构造函数，接受一个参数 input_dim，表示 Wide 部分和 Deep 部分的输入维度，默认值为 (8, 2)。\n",
    "    # input_dim[0]: Wide 部分的输入维度。\n",
    "    # input_dim[1]: Deep 部分的输入维度。\n",
    "    def __init__(self, input_dim=(8, 2)):\n",
    "        super().__init__()\n",
    "\n",
    "        # 定义 Deep 部分，使用 nn.Sequential 构建一个包含两个全连接层和 ReLU 激活函数的神经网络。\n",
    "        # nn.Linear(input_dim[1], 30): 输入维度为 input_dim[1]，输出维度为 30。\n",
    "        # nn.ReLU(): ReLU 激活函数。\n",
    "        # nn.Linear(30, 30): 第二个全连接层，输入和输出维度均为 30。\n",
    "        # Deep 部分用于提取输入特征的高阶交互信息。\n",
    "        self.deep = nn.Sequential(\n",
    "            nn.Linear(input_dim[1], 30),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 30),\n",
    "            nn.ReLU()\n",
    "        )\n",
    "\n",
    "        # 定义 Wide 部分，使用 nn.Linear 构建一个全连接层，输入维度为 input_dim[0]，输出维度为 1。\n",
    "        # 将 Wide 部分（原始输入特征）和 Deep 部分（提取的特征）拼接后，通过一个全连接层输出最终结果。\n",
    "        self.output_layer = nn.Linear(30 + input_dim[0], 1)\n",
    "\n",
    "        # 初始化权重\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, nn.Linear):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "\n",
    "    # 定义前向传播方法，接受两个输入：\n",
    "    # x_wide: Wide 部分的输入特征。\n",
    "    # x_deep: Deep 部分的输入特征。\n",
    "    # 原理: 将 Wide 部分和 Deep 部分的输入分别处理，然后结合它们的输出。\n",
    "    def forward(self, x_wide, x_deep):\n",
    "\n",
    "        # 将 Deep 部分的输入 x_deep 传递给 Deep 部分，得到 Deep 部分的输出 deep_output\n",
    "        deep_output = self.deep(x_deep)\n",
    "\n",
    "        # 将 Wide 部分的输入 x_wide 和 Deep 部分的输出 deep_output 在维度 1 上拼接\n",
    "        # 结合 Wide 部分（原始特征）和 Deep 部分（提取的特征），增强模型的表达能力\n",
    "        concat = torch.cat([x_wide, deep_output], dim=1)\n",
    "        logits = self.output_layer(concat)\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ],
   "id": "fc7613b2964490ed",
   "outputs": [],
   "execution_count": 12
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:48:10.340331Z",
     "start_time": "2025-02-07T02:48:10.336835Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  #容忍度\n",
    "        self.min_delta = min_delta  #最小变化量\n",
    "        self.best_metric = -1  #最佳指标\n",
    "        self.counter = 0  #计数器\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            self.best_metric = metric  #更新最佳指标\n",
    "            self.counter = 0  #计数器清零\n",
    "        else:\n",
    "            self.counter += 1  #计数器加一\n",
    "\n",
    "    @property  #定义一个属性，用于判断是否满足early stop条件\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience"
   ],
   "id": "5d0ed9f38f94f1cd",
   "outputs": [],
   "execution_count": 13
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:53:59.463811Z",
     "start_time": "2025-02-07T02:53:59.459811Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "@torch.no_grad()  #不计算梯度\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []  #损失列表\n",
    "    for (datas_deep, datas_wide), labels in dataloader:\n",
    "        datas_deep = datas_deep.to(device)\n",
    "        datas_wide = datas_wide.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas_deep, datas_wide)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        loss_list.append(loss.item())  # 损失列表添加损失值\n",
    "\n",
    "    return np.mean(loss_list)  # 计算平均损失值"
   ],
   "id": "60df88bcd380c661",
   "outputs": [],
   "execution_count": 19
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:54:00.531262Z",
     "start_time": "2025-02-07T02:54:00.526764Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model = WideDeep()\n",
    "model"
   ],
   "id": "6216d2714dd3ae30",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "WideDeep(\n",
       "  (deep): Sequential(\n",
       "    (0): Linear(in_features=2, out_features=30, bias=True)\n",
       "    (1): ReLU()\n",
       "    (2): Linear(in_features=30, out_features=30, bias=True)\n",
       "    (3): ReLU()\n",
       "  )\n",
       "  (output_layer): Linear(in_features=38, out_features=1, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 20
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 训练模型",
   "id": "a7c7dd14dd2f85cb"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:54:02.287748Z",
     "start_time": "2025-02-07T02:54:02.282744Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 训练\n",
    "def training(\n",
    "        model,  #模型\n",
    "        train_loader,  #训练集\n",
    "        val_loader,  #验证集\n",
    "        epoch,  #训练轮数\n",
    "        loss_fct,  #损失函数\n",
    "        optimizer,  #优化器\n",
    "        early_stop_callback=None,  #早停回调函数\n",
    "        eval_step=500,  #每隔多少步进行一次验证集验证\n",
    "):\n",
    "    # 记录训练过程\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0  #全局步数\n",
    "    model.train()  #训练模式\n",
    "\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for (datas_deep, datas_wide), labels in train_loader:  #和上面自定义的dataset是一致的\n",
    "                datas_deep = datas_deep.to(device)\n",
    "                datas_wide = datas_wide.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas_deep, datas_wide)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "\n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "\n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict"
   ],
   "id": "9260fc04b5320939",
   "outputs": [],
   "execution_count": 21
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:54:17.239455Z",
     "start_time": "2025-02-07T02:54:02.972165Z"
    }
   },
   "cell_type": "code",
   "source": [
    "epoch = 10\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.0)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    ")"
   ],
   "id": "59b36c8d0ea49c7b",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/14520 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "8dcae5fd38c44b9bb444a7b249e5fe78"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n"
     ]
    }
   ],
   "execution_count": 22
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:54:18.440717Z",
     "start_time": "2025-02-07T02:54:18.379020Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "\n",
    "plot_learning_curves(record, sample_step=500)  #横坐标是 steps"
   ],
   "id": "f5569aee8d9d6231",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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btw4dSIiIiLy+gynRERERCVh8EFERESGYvBBREREhmLwQURERIby+QynRelLzZRnmtbyTEkrc8/Ltv1pVjhvY7vZbrMwa9vZbrbbH+jHbXeWjPO74OP8+fPqXNZ3ISIiosAix/Fq1aoZu6ptZclsqMePH0dMTAwsFotHt60vWnfkyBGPL1rnz9huttsszNp2tpvt9gcSTkjg0aBBA1it1sDq+ZAdbtSokVefQ94sf3rDjMJ2m4tZ223mtrPd5hLrh+0uq8dDx4RTIiIiMhSDDyIiIjKUqYKPsLAwPPvss+rcTNhuttsszNp2tpvtDjR+l3BKREREVZupej6IiIjI9xh8EBERkaEYfBAREZGhGHwQERGRoUwTfEydOhVNmjRBeHg4Lr74YqxevRqB5KWXXkL37t3VzK916tTB8OHDsWvXrkKPyc7OxtixY1GrVi1ER0fj+uuvx8mTJws95vDhw7j66qsRGRmptvPYY48hPz+/0GMWLlyILl26qEzq5s2b47PPPoM/ePnll9Wstw8//LAp2nzs2DHccsstqm0RERHo0KED1q5d67xfcsWfeeYZ1K9fX90/YMAA7Nmzp9A2zpw5g9GjR6uJiKpXr4677roL6enphR6zefNmXHrppepvQ2ZNnDx5MnyloKAATz/9NBITE1WbmjVrhkmTJhVaK6IqtHvx4sUYOnSomglSPtM///xzofuNbOP333+P1q1bq8fIZ2z27NnwVdtlzZLHH39c7UdUVJR6zG233aZmvQ70tpf1nru677771GOmTJkS8O0ukWYC06dP10JDQ7VPPvlE27Ztm3b33Xdr1atX106ePKkFisGDB2uffvqptnXrVm3jxo3akCFDtMaNG2vp6enOx9x3331afHy89ueff2pr167VLrnkEq1Xr17O+/Pz87X27dtrAwYM0DZs2KDNnj1bi4uL05588knnY/bv369FRkZq48eP17Zv36698847WlBQkPbbb79pvrR69WqtSZMm2kUXXaQ99NBDVb7NZ86c0RISErTbb79dW7VqldrH33//Xdu7d6/zMS+//LJWrVo17eeff9Y2bdqkXXvttVpiYqKWlZXlfMyVV16pdezYUVu5cqW2ZMkSrXnz5trNN9/svD81NVWrW7euNnr0aPXZ+uabb7SIiAjtww8/1HzhhRde0GrVqqXNnDlTO3DggPb9999r0dHR2ltvvVWl2i2fw6eeekr76aefJKrSZsyYUeh+o9q4bNky9VmfPHmy+uz/5z//0UJCQrQtW7b4pO3nzp1Tf6vffvuttnPnTm3FihVajx49tK5duxbaRiC2vaz3XCf3S9saNGigvfnmm1qgt7skpgg+5MM7duxY5/WCggL1xr700ktaoEpOTlYf4EWLFjn/aOUDJF/Wuh07dqjHyB+w/uG3Wq1aUlKS8zHvv/++Fhsbq+Xk5Kjr//rXv7R27doVeq4bb7xRBT++cv78ea1FixbavHnztMsuu8wZfFTlNj/++ONanz59SrzfZrNp9erV01599VXnbfJ6hIWFqS8cIV8s8lqsWbPG+Zg5c+ZoFotFO3bsmLr+3nvvaTVq1HC+Fvpzt2rVSvOFq6++WrvzzjsL3XbdddepL9Oq2u6iByIj2zhy5Ej1mru6+OKLtXvvvVczQmkHYdcfHvK4Q4cOVZm2o4R2Hz16VGvYsKEKHOTHh2vwURXa7arKD7vk5uZi3bp1qtvSdf0Yub5ixQoEqtTUVHVes2ZNdS5tlC5L13ZKt1rjxo2d7ZRz6WKrW7eu8zGDBw9WixRt27bN+RjXbeiP8eVrJcMqMmxSdL+qcpt//fVXdOvWDTfccIMaKurcuTM+/vhj5/0HDhxAUlJSof2WNRVkSNG17dI1K9vRyePl879q1SrnY/r27YvQ0NBCbZchvbNnz8JovXr1wp9//ondu3er65s2bcLSpUtx1VVXVel2uzKyjf742S/uu06GIKS9VbntNpsNt956qxoWbteu3QX3V7V2V/ngIyUlRY0jux58hFyXP/BAJB9SyXvo3bs32rdvr26TtsgHTv8DLa6dcl7c66DfV9pj5GCdlZUFo02fPh3r169XOS9FVdU2i/379+P9999HixYt8Pvvv+P+++/Hgw8+iM8//7zQvpf2uZZzCVxcBQcHq4C1PK+PkZ544gncdNNNKogMCQlRQZd81mWcuyq325WRbSzpMb5+DVxzuiQH5Oabb3YuoFZV2/7KK6+odsjfeXGqWrv9blVbcq8nYOvWreoXYVUmy0U/9NBDmDdvnkqMMhMJMOUXzosvvqiuy0FY3vMPPvgAY8aMQVX13Xff4euvv8a0adPUr7+NGzeq4EOS9Kpyu+lC0qs5cuRIlXwrgXhVtm7dOrz11lvqh5b08phBle/5iIuLQ1BQ0AUVEHK9Xr16CDTjxo3DzJkzsWDBAjRq1Mh5u7RFhpjOnTtXYjvlvLjXQb+vtMfIrw7Jujf6DzI5OVlVoUiEL6dFixbh7bffVpclWq9qbdZJlUPbtm0L3damTRtVueO676V9ruVcXj9XUuUjGfPleX2MJF3Oeu+HDJdJN/Qjjzzi7Pmqqu12ZWQbS3qMr18DPfA4dOiQ+vHhumx8VWz7kiVLVJtkyFj/rpO2P/roo6pKsyq2u8oHH9It37VrVzWO7PqrUq737NkTgUKifwk8ZsyYgfnz56tSRFfSRummdm2njPPJwUpvp5xv2bKl0AdY/8PWD3TyGNdt6I/xxWvVv39/tb/y61c/SW+AdMHrl6tam3UypFa0lFryIBISEtRlef/ly8J1v2WYSMZ+XdsugZkEcTr57MjnX/IH9MdICaB82bu2vVWrVqhRowaMlpmZqcawXcmPB9nnqtxuV0a20R8/+3rgIaXFf/zxhyo1d1UV237rrbeqElnX7zrp7ZNgXIZdq2S7NZOU2kqm+GeffaYyhu+55x5VautaAeHv7r//flV6t3DhQu3EiRPOU2ZmZqGyUym/nT9/vio77dmzpzoVLTsdNGiQKteVUtLatWsXW3b62GOPqcqRqVOn+rzs1JVrtUtVbrNk+AcHB6vS0z179mhff/212sevvvqqUDmmfI5/+eUXbfPmzdqwYcOKLcfs3LmzKtddunSpqhpyLc2TKgopzbv11ltVhr38rcjz+KrUdsyYMSrbXy+1lbJDKY2WiqSq1G6p4JLSbznJ1/Abb7yhLusVHUa1Ucou5XP22muvqc/+s88+6/Wyy9Lanpubq8qKGzVqpP5eXb/rXCs4ArHtZb3nRRWtdgnUdpfEFMGHkLkb5CAl831I6a3USQcS+bAWd5K5P3TyxfTAAw+oUiv5wI0YMUL90bo6ePCgdtVVV6nab/lSf/TRR7W8vLxCj1mwYIHWqVMn9Vo1bdq00HP4W/BRldv8v//9TwVOEji3bt1a++ijjwrdLyWZTz/9tPqykcf0799f27VrV6HHnD59Wn05yVwZUl58xx13qC9BVzKPhJT1yjbkwC8HPl9JS0tT76/8rYaHh6v3QuZGcD3wVIV2y+etuL9nCb6MbuN3332ntWzZUn32peR81qxZPmu7BJwlfdfJ/wvktpf1nrsTfARiu0tikX+M7WshIiIiM6vyOR9ERETkXxh8EBERkaEYfBAREZGhGHwQERGRoRh8EBERkaEYfBAREZGhGHwQERGRoRh8EBERkaEYfBAREZGhGHwQkcfdfvvtGD58uK93g4j8FIMPIiIiMhSDDyKqsB9++AEdOnRARESEWvp8wIABahnwzz//HL/88gssFos6LVy4UD3+yJEjarn06tWro2bNmhg2bBgOHjx4QY/JxIkTUbt2bcTGxuK+++5Dbm6uD1tJRJ4W7PEtEpEpnDhxAjfffDMmT56MESNG4Pz581iyZAluu+02HD58GGlpafj000/VYyXQyMvLw+DBg9GzZ0/1uODgYDz//PO48sorsXnzZoSGhqrH/vnnnwgPD1cBiwQmd9xxhwpsXnjhBR+3mIg8hcEHEVU4+MjPz8d1112HhIQEdZv0ggjpCcnJyUG9evWcj//qq69gs9nw3//+V/WGCAlOpBdEAo1Bgwap2yQI+eSTTxAZGYl27drhueeeU70pkyZNgtXKzlqiqoB/yURUIR07dkT//v1VwHHDDTfg448/xtmzZ0t8/KZNm7B3717ExMQgOjpanaRHJDs7G/v27Su0XQk8dNJTkp6eroZsiKhqYM8HEVVIUFAQ5s2bh+XLl2Pu3Ll455138NRTT2HVqlXFPl4CiK5du+Lrr7++4D7J7yAi82DwQUQVJsMnvXv3VqdnnnlGDb/MmDFDDZ0UFBQUemyXLl3w7bffok6dOiqRtLQekqysLDV0I1auXKl6SeLj473eHiIyBoddiKhCpIfjxRdfxNq1a1WC6U8//YRTp06hTZs2aNKkiUoi3bVrF1JSUlSy6ejRoxEXF6cqXCTh9MCBAyrX48EHH8TRo0ed25XKlrvuugvbt2/H7Nmz8eyzz2LcuHHM9yCqQtjzQUQVIr0XixcvxpQpU1Rli/R6vP7667jqqqvQrVs3FVjIuQy3LFiwAP369VOPf/zxx1WSqlTHNGzYUOWNuPaEyPUWLVqgb9++KmlVKmomTJjg07YSkWdZNE3TPLxNIqIKkXk+zp07h59//tnXu0JEXsR+TCIiIjIUgw8iIiIyFIddiIiIyFDs+SAiIiJDMfggIiIiQzH4ICIiIkMx+CAiIiJDMfggIiIiQzH4ICIiIkMx+CAiIiJDMfggIiIiGOn/Ae5+gs9nZ+tvAAAAAElFTkSuQmCC"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 23
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 测试模型",
   "id": "fa9189e3c38c7429"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:54:32.926890Z",
     "start_time": "2025-02-07T02:54:32.871897Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "id": "6cb190c77ce90e29",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.5244\n"
     ]
    }
   ],
   "execution_count": 24
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "f848b3bdcf1b6c74"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
